if {"::tcltest" ni [namespace children]} {
	package require tcltest
	namespace import ::tcltest::*
}

package require rl_json
namespace path {::rl_json}

test template-0.1 {Empty action list} -body { #<<<
	string cat \n [join [lmap {k a b} [json template_actions {
		{
			"x":	"y"
		}
	}] {
		format {%20s  %20s  %20s} $k $a $b
	}] \n] \n
} -cleanup {
	unset -nocomplain k a b
} -result "\n\n"
#>>>
test template-0.2 {Minimal action list} -body { #<<<
	string cat \n [join [lmap {k a b} [json template_actions {"~S:foo"}] {
		format {%20s  %20s  %20s} $k $a $b
	}] \n] \n
} -cleanup {
	unset -nocomplain k a b
} -result {
            ALLOCATE                     0                     2
         FETCH_VALUE                   foo                      
        STORE_STRING                                           1
        REPLACE_ATOM                                           1
}
#>>>
test template-0.3 {No replacement} -body { #<<<
	json template {"foo"}
} -result {"foo"}
#>>>
test template-0.4 {Minimal replacement: null} -body { #<<<
	json template {"~S:foo"}
} -result null
#>>>
test template-0.5 {Minimal replacement: empty string} -body { #<<<
	json template {"~S:foo"} {foo ""}
} -result {""}
#>>>
test template-0.6 {Minimal replacement: string < STRING_DEDUP_MAX bytes} -body { #<<<
	json template {"~S:foo"} {foo "small"}
} -result {"small"}
#>>>
test template-0.7 {Minimal replacement: string > STRING_DEDUP_MAX bytes} -body { #<<<
	json template {"~S:foo"} [list foo [string repeat a 256]]
} -result "\"[string repeat a 256]\""
#>>>
test template-0.8 {Leak: Tcl_ListObjAppendList realloc} -body { #<<<
	json template {["~S:foo"]} {}
} -result {[null]}
#>>>
test template-0.9 {Leak: Non-string key subst: number} -body { #<<<
	json template { {"~N:v":"bar"} } {v 1}
} -returnCodes error -result {Only strings allowed as object keys}
#>>>
test template-0.10 {Leak: Test interpolated numeric validation} -body { #<<<
	json template {["~S:foo"]} {}
	list [json template {"~S:bar"} {}]
} -result {null}
#>>>
test template-1.1 {Produce a JSON doc with interpolated values, no dict} -setup { #<<<
	set bar	"Bar"
	unset -nocomplain a
	array set a {
		x		str
		y		123
		on		yes
		off		0
		subdoc	{{"inner": "~S:bar"}}
		subdoc2	{{"inner2": "~S:bar"}}
		subdoc3	null
		empty	""
	}
} -body {
	json template {
		{
			"foo": "~S:bar",
			"baz":  [
				"~S:a(empty)",
				"~S:a(x)",
				"~N:a(y)",
				123.4,
				"~B:a(on)",
				"~B:a(off)",
				"~S:a(not_defined)",
				"~L:~S:not a subst",
				"~J:a(subdoc)",
				"~T:a(subdoc2)",
				"~J:a(subdoc3)"
			]
		}
	}
} -cleanup {
	unset -nocomplain bar a
} -result {{"foo":"Bar","baz":["","str",123,123.4,true,false,null,"~S:not a subst",{"inner":"~S:bar"},{"inner2":"Bar"},null]}}
#>>>
test template-1.1.1 {Action list for 1.1} -body { #<<<
	string cat \n [join [lmap {k a b} [json template_actions {
		{
			"x":	"y",
			"foo": "~S:bar",
			"unchanged":	["a","b","c"],
			"~S:x":  [
				"~S:a(empty)",
				"~S:a(x)",
				"~N:a(y)",
				123.4,
				"~B:a(on)",
				"~B:a(off)",
				"~S:a(not_defined)",
				"~L:~S:not a subst",
				"~J:a(subdoc)",
				"~T:a(subdoc2)",
				"~J:a(subdoc3)"
			]
		}
	}] {
		format {%20s  %20s  %20s} $k $a $b
	}] \n] \n
} -cleanup {
	unset -nocomplain k a b
} -result {
            ALLOCATE                     2                    13
         FETCH_VALUE                   bar                      
        STORE_STRING                                           1
         FETCH_VALUE              a(empty)                      
        STORE_STRING                                           2
         FETCH_VALUE                  a(x)                      
        STORE_STRING                                           3
         FETCH_VALUE                  a(y)                      
        STORE_NUMBER                                           4
         FETCH_VALUE                 a(on)                      
       STORE_BOOLEAN                                           5
         FETCH_VALUE                a(off)                      
       STORE_BOOLEAN                                           6
         FETCH_VALUE        a(not_defined)                      
        STORE_STRING                                           7
     DECLARE_LITERAL      "~S:not a subst"                      
          STORE_JSON                                           8
         FETCH_VALUE             a(subdoc)                      
          STORE_JSON                                           9
         FETCH_VALUE            a(subdoc2)                      
      STORE_TEMPLATE                                          10
         FETCH_VALUE            a(subdoc3)                      
          STORE_JSON                                          11
         FETCH_VALUE                     x                      
        STORE_STRING                                          12
         PUSH_TARGET  
		{
			"x":	"y",
			"foo": "~S:bar",
			"unchanged":	["a","b","c"],
			"~S:x":  [
				"~S:a(empty)",
				"~S:a(x)",
				"~N:a(y)",
				123.4,
				"~B:a(on)",
				"~B:a(off)",
				"~S:a(not_defined)",
				"~L:~S:not a subst",
				"~J:a(subdoc)",
				"~T:a(subdoc2)",
				"~J:a(subdoc3)"
			]
		}
	                      
         REPLACE_VAL                   foo                     1
         PUSH_TARGET  ["~S:a(empty)","~S:a(x)","~N:a(y)",123.4,"~B:a(on)","~B:a(off)","~S:a(not_defined)","~L:~S:not a subst","~J:a(subdoc)","~T:a(subdoc2)","~J:a(subdoc3)"]                      
         REPLACE_ARR                     0                     2
         REPLACE_ARR                     1                     3
         REPLACE_ARR                     2                     4
         REPLACE_ARR                     4                     5
         REPLACE_ARR                     5                     6
         REPLACE_ARR                     6                     7
         REPLACE_ARR                     7                     8
         REPLACE_ARR                     8                     9
         REPLACE_ARR                     9                    10
         REPLACE_ARR                    10                    11
          POP_TARGET                                            
         REPLACE_VAL                  ~S:x                     0
         REPLACE_KEY                  ~S:x                    12
          POP_TARGET                                            
        REPLACE_ATOM                                           0
}
#>>>
test template-1.2 {Produce a JSON doc with interpolated values, using dict} -body { #<<<
	json template {
		{
			"foo": "~S:bar",
			"baz":  [
				"~S:empty",
				"~S:x",
				"~N:y",
				123.4,
				"~B:on",
				"~B:off",
				"~S:not_defined",
				"~L:~S:not a subst",
				"~J:subdoc",
				"~T:subdoc2",
				"~J:subdoc3",
				"~S:baz",
				"~S:ts",
				"~S:tn",
				"~S:tb",
				"~S:tj",
				"~S:tt",
				"~S:tl",
				"~S:tx",
				"~N:nonesuch",
				"~B:nonesuch",
				"~J:nonesuch"
			]
		}
	} {
		foo		"Foo"
		bar		"Ba\"r"
		baz		"Baz"
		x		str
		y		123
		on		yes
		off		0
		subdoc	{{"inner" : "~S:foo"}}
		subdoc2	{{"inner2" : "~S:foo"}}
		subdoc3	null
		empty	{}
		ts		~S:foo
		tn		~N:foo
		tb		~B:foo
		tj		~J:foo
		tt		~T:foo
		tl		~L:~S:foo
		tx		~X:foo
	}
} -result {{"foo":"Ba\"r","baz":["","str",123,123.4,true,false,null,"~S:not a subst",{"inner":"~S:foo"},{"inner2":"Foo"},null,"Baz","~S:foo","~N:foo","~B:foo","~J:foo","~T:foo","~L:~S:foo","~X:foo",null,null,null]}}
#>>>
test template-1.3 {Produce a JSON doc with interpolated values, subst object keys, using dict} -body { #<<<
	json template {
		{
			"~S:foo": "~S:bar",
			"baz":  [
				"~S:x",
				"~N:y",
				123.4,
				"~B:on",
				"~B:off",
				"~S:not_defined",
				"~L:~S:not a subst",
				"~J:subdoc",
				"~T:subdoc2",
				"~J:subdoc3",
				"~S:baz"
			]
		}
	} {
		foo		"Foo"
		bar		"Ba\"r"
		baz		"Baz"
		x		str
		y		123
		on		yes
		off		0
		subdoc	{{"inner" : "~S:foo"}}
		subdoc2	{{"inner2" : "~S:foo"}}
		subdoc3	null
	}
} -result {{"baz":["str",123,123.4,true,false,null,"~S:not a subst",{"inner":"~S:foo"},{"inner2":"Foo"},null,"Baz"],"Foo":"Ba\"r"}}
#>>>
test template-1.3.1 {Action list for 1.3} -body { #<<<
	string cat \n [join [lmap {k a b} [json template_actions {
		{
			"~S:foo": "~S:bar",
			"baz":  [
				"~S:x",
				"~N:y",
				123.4,
				"~B:on",
				"~B:off",
				"~S:not_defined",
				"~L:~S:not a subst",
				"~J:subdoc",
				"~T:subdoc2",
				"~J:subdoc3",
				"~S:baz"
			]
		}
	}] {
		format {%20s  %20s  %20s} $k $a $b
	}] \n] \n
} -cleanup {
	unset -nocomplain k a b
} -result {
            ALLOCATE                     2                    13
         FETCH_VALUE                   bar                      
        STORE_STRING                                           1
         FETCH_VALUE                   foo                      
        STORE_STRING                                           2
         FETCH_VALUE                     x                      
        STORE_STRING                                           3
         FETCH_VALUE                     y                      
        STORE_NUMBER                                           4
         FETCH_VALUE                    on                      
       STORE_BOOLEAN                                           5
         FETCH_VALUE                   off                      
       STORE_BOOLEAN                                           6
         FETCH_VALUE           not_defined                      
        STORE_STRING                                           7
     DECLARE_LITERAL      "~S:not a subst"                      
          STORE_JSON                                           8
         FETCH_VALUE                subdoc                      
          STORE_JSON                                           9
         FETCH_VALUE               subdoc2                      
      STORE_TEMPLATE                                          10
         FETCH_VALUE               subdoc3                      
          STORE_JSON                                          11
         FETCH_VALUE                   baz                      
        STORE_STRING                                          12
         PUSH_TARGET  
		{
			"~S:foo": "~S:bar",
			"baz":  [
				"~S:x",
				"~N:y",
				123.4,
				"~B:on",
				"~B:off",
				"~S:not_defined",
				"~L:~S:not a subst",
				"~J:subdoc",
				"~T:subdoc2",
				"~J:subdoc3",
				"~S:baz"
			]
		}
	                      
         REPLACE_VAL                ~S:foo                     1
         REPLACE_KEY                ~S:foo                     2
         PUSH_TARGET  ["~S:x","~N:y",123.4,"~B:on","~B:off","~S:not_defined","~L:~S:not a subst","~J:subdoc","~T:subdoc2","~J:subdoc3","~S:baz"]                      
         REPLACE_ARR                     0                     3
         REPLACE_ARR                     1                     4
         REPLACE_ARR                     3                     5
         REPLACE_ARR                     4                     6
         REPLACE_ARR                     5                     7
         REPLACE_ARR                     6                     8
         REPLACE_ARR                     7                     9
         REPLACE_ARR                     8                    10
         REPLACE_ARR                     9                    11
         REPLACE_ARR                    10                    12
          POP_TARGET                                            
         REPLACE_VAL                   baz                     0
          POP_TARGET                                            
        REPLACE_ATOM                                           0
}
#>>>
test template-1.4 {Invalid interpolated type key fallthrough} -body { #<<<
	json template {
		{
			"foo": "~S:foo",
			"bar": "~A:bar"
		}
	} {
	}
} -result {{"foo":null,"bar":"~A:bar"}}
#>>>
test template-2.1 {Test interpolated numeric validation} -body { #<<<
	json template {
		{
			"Foo": "~N:num",
			"Bar": "baz"
		}
	} {
		num	""
	}
} -returnCodes error -result {Error substituting value from "num" into template, not a number: ""}
#>>>
test template-2.2.1 {Atomic string} -body { #<<<
	set template	{"~S:num"}
	list [json template $template {num " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {{" 1"} {ALLOCATE 0 2 FETCH_VALUE num {} STORE_STRING {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.2 {Atomic number} -body { #<<<
	set template	{"~N:num"}
	list [json template $template {num " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {1 {ALLOCATE 0 2 FETCH_VALUE num {} STORE_NUMBER {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.3 {Atomic boolean} -body { #<<<
	set template	{"~B:num"}
	list [json template $template {num " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {true {ALLOCATE 0 2 FETCH_VALUE num {} STORE_BOOLEAN {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.4.1 {Atomic null: string} -body { #<<<
	set template	{"~S:num"}
	list [json template $template {numx " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {null {ALLOCATE 0 2 FETCH_VALUE num {} STORE_STRING {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.4.2 {Atomic null: number} -body { #<<<
	set template	{"~N:num"}
	list [json template $template {numx " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {null {ALLOCATE 0 2 FETCH_VALUE num {} STORE_NUMBER {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.4.3 {Atomic null: boolean} -body { #<<<
	set template	{"~B:num"}
	list [json template $template {numx " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {null {ALLOCATE 0 2 FETCH_VALUE num {} STORE_BOOLEAN {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.4.4 {Atomic null: json} -body { #<<<
	set template	{"~J:num"}
	list [json template $template {numx " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {null {ALLOCATE 0 2 FETCH_VALUE num {} STORE_JSON {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.4.5 {Atomic null: template} -body { #<<<
	set template	{"~T:num"}
	list [json template $template {numx " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {null {ALLOCATE 0 2 FETCH_VALUE num {} STORE_TEMPLATE {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-2.2.4.6 {Atomic: literal} -body { #<<<
	set template	{"~L:num"}
	list [json template $template {numx " 1"}] [json template_actions $template]
} -cleanup {
	unset -nocomplain template
} -result {{"num"} {ALLOCATE 0 2 DECLARE_LITERAL num {} STORE_STRING {} 1 REPLACE_ATOM {} 1}}
#>>>
test template-3.1 {Non-string key subst: number} -body { #<<<
	list [catch {
		json template { {"~N:v":"bar"} } {v 1}
} r o] [expr {[dict exists $o -errorcode] ? [dict get $o -errorcode] : ""}] $r
} -cleanup {
	unset -nocomplain r o
} -result [list 1 NONE {Only strings allowed as object keys}]
#>>>
test template-3.2 {Non-string key subst: true} -body { #<<<
	list [catch {
		json template { {"~B:v":"bar"} } {v 1}
} r o] [expr {[dict exists $o -errorcode] ? [dict get $o -errorcode] : ""}] $r
} -cleanup {
	unset -nocomplain r o
} -result [list 1 NONE {Only strings allowed as object keys}]
#>>>
test template-3.3 {Non-string key subst: false} -body { #<<<
	list [catch {
		json template { {"~B:v":"bar"} } {v 0}
} r o] [expr {[dict exists $o -errorcode] ? [dict get $o -errorcode] : ""}] $r
} -cleanup {
	unset -nocomplain r o
} -result [list 1 NONE {Only strings allowed as object keys}]
#>>>
test template-3.4 {Non-string key subst: null} -body { #<<<
	list [catch {
		json template { {"~S:v":"bar"} } {}
	} r o] [expr {[dict exists $o -errorcode] ? [dict get $o -errorcode] : ""}] $r
} -cleanup {
	unset -nocomplain r o
} -result [list 1 NONE {Only strings allowed as object keys, got: null for key "~S:v"}]
#>>>
test template-3.5 {Non-string key subst: literal} -body { #<<<
	json template { {"~L:~N:v":"bar"} } {}
} -result {{"~N:v":"bar"}}
#>>>
test template-3.5.1 {Non-string key subst: literal} -body { #<<<
	json template_actions { {"~L:~N:v":"bar"} }
} -result {ALLOCATE 1 2 DECLARE_LITERAL {"~N:v"} {} STORE_JSON {} 1 PUSH_TARGET { {"~L:~N:v":"bar"} } {} REPLACE_KEY ~L:~N:v 1 POP_TARGET {} {} REPLACE_ATOM {} 0}
#>>>
test template-3.5.2 {Non-string key subst: literal, shorter than pref} -body { #<<<
	json template_actions { {"~L:v":"bar"} }
} -result {ALLOCATE 1 2 DECLARE_LITERAL v {} STORE_STRING {} 1 PUSH_TARGET { {"~L:v":"bar"} } {} REPLACE_KEY ~L:v 1 POP_TARGET {} {} REPLACE_ATOM {} 0}
#>>>
test template-3.5.3 {Non-string key subst: literal, empty} -body { #<<<
	json template_actions { {"~L:":"bar"} }
} -result {ALLOCATE 1 2 DECLARE_LITERAL {} {} STORE_STRING {} 1 PUSH_TARGET { {"~L:":"bar"} } {} REPLACE_KEY ~L: 1 POP_TARGET {} {} REPLACE_ATOM {} 0}
#>>>
test template-3.5.4 {Non-string key subst: literal, empty, no ~} -body { #<<<
	json template_actions { {"~L:xS:":"bar"} }
} -result {ALLOCATE 1 2 DECLARE_LITERAL xS: {} STORE_STRING {} 1 PUSH_TARGET { {"~L:xS:":"bar"} } {} REPLACE_KEY ~L:xS: 1 POP_TARGET {} {} REPLACE_ATOM {} 0}
#>>>
test template-3.5.5 {Non-string key subst: literal, empty, no :} -body { #<<<
	json template_actions { {"~L:~S/":"bar"} }
} -result {ALLOCATE 1 2 DECLARE_LITERAL ~S/ {} STORE_STRING {} 1 PUSH_TARGET { {"~L:~S/":"bar"} } {} REPLACE_KEY ~L:~S/ 1 POP_TARGET {} {} REPLACE_ATOM {} 0}
#>>>
test template-3.5.6 {Non-string key subst: literal, empty, not a valid letter} -body { #<<<
	json template_actions { {"~L:~X:":"bar"} }
} -result {ALLOCATE 1 2 DECLARE_LITERAL ~X: {} STORE_STRING {} 1 PUSH_TARGET { {"~L:~X:":"bar"} } {} REPLACE_KEY ~L:~X: 1 POP_TARGET {} {} REPLACE_ATOM {} 0}
#>>>

try { # Check string quoting
	set leading		"fooは"
	set trailing	"barほ"
	for {set i 0} {$i <= 0x1F} {incr i} {
		lappend range	[format 0x%02X $i]
	}
	lappend range 0x22 0x5C 0x2F
	set special	{
		0x08	{\b}
		0x09	{\t}
		0x0A	{\n}
		0x0C	{\f}
		0x0D	{\r}
		0x22	{\"}
		0x5C	{\\}
		0x2F	{/}
	}
	foreach c $range {
		set char	[format %c $c]
		if {[dict exists $special $c]} {
			set expect	[dict get $special $c]
		} else {
			set expect	[format {\u%04X} $c]
		}
		test template-4.$c.1 "String quoting: $c -> $expect, no leading, no trailing" -setup { #<<<
			set str	$char
		} -body {
			json template {"~S:str"}
		} -cleanup {
			unset -nocomplain str
		} -result "\"$expect\""
		#>>>
		test template-4.$c.2 "String quoting: $c -> $expect, leading, no trailing" -setup { #<<<
			set str	"$leading$char"
		} -body {
			json template {"~S:str"}
		} -cleanup {
			unset -nocomplain str
		} -result "\"$leading$expect\""
		#>>>
		test template-4.$c.3 "String quoting: $c -> $expect, no leading, trailing" -setup { #<<<
			set str	"$char$trailing"
		} -body {
			json template {"~S:str"}
		} -cleanup {
			unset -nocomplain str
		} -result "\"$expect$trailing\""
		#>>>
		test template-4.$c.4 "String quoting: $c -> $expect, leading, trailing" -setup { #<<<
			set str	"$leading$char$trailing"
		} -body {
			json template {"~S:str"}
		} -cleanup {
			unset -nocomplain str
		} -result "\"$leading$expect$trailing\""
		#>>>
	}
} finally {
	unset -nocomplain c char expect range i special
}

test template-5.1 {Not quite a template} -body { #<<<
	json template {
		{
			"foo": "~X:bar",
			"~s:bar": null
		}
	}
} -result {{"foo":"~X:bar","~s:bar":null}}
#>>>
test template-5.1.1 {Actions for 5.1} -body { #<<<
	json template_actions {
		{
			"foo": "~X:bar",
			"~s:ar": null
		}
	}
} -result {}
#>>>
test template-6.1 {too few args} -body { #<<<
	json template 
} -returnCodes error -result {wrong # args: should be "*template json_template ?source_dict?"} -match glob
#>>>
test template-6.2 {too many args} -body { #<<<
	json template {"~S:foo"} bar baz
} -returnCodes error -result {wrong # args: should be "*template json_template ?source_dict?"} -match glob
#>>>
test template-7.1 {Action list: optimize fetches} -body { #<<<
} -body {
	string cat \n [join [lmap {k a b} [json template_actions {
		{
			"foo": "~S:bar",
			"~S:bar":  [
				"~N:bar",
				"~B:bar",
				"~J:bar",
				"~L:~S:not a subst"
			]
		}
	}] {
		format {%20s  %20s  %20s} $k $a $b
	}] \n] \n
} -cleanup {
	unset -nocomplain k a b
} -result {
            ALLOCATE                     2                     6
         FETCH_VALUE                   bar                      
        STORE_STRING                                           1
        STORE_NUMBER                                           2
       STORE_BOOLEAN                                           3
          STORE_JSON                                           4
     DECLARE_LITERAL      "~S:not a subst"                      
          STORE_JSON                                           5
         PUSH_TARGET  
		{
			"foo": "~S:bar",
			"~S:bar":  [
				"~N:bar",
				"~B:bar",
				"~J:bar",
				"~L:~S:not a subst"
			]
		}
	                      
         REPLACE_VAL                   foo                     1
         PUSH_TARGET  ["~N:bar","~B:bar","~J:bar","~L:~S:not a subst"]                      
         REPLACE_ARR                     0                     2
         REPLACE_ARR                     1                     3
         REPLACE_ARR                     2                     4
         REPLACE_ARR                     3                     5
          POP_TARGET                                            
         REPLACE_VAL                ~S:bar                     0
         REPLACE_KEY                ~S:bar                     1
          POP_TARGET                                            
        REPLACE_ATOM                                           0
}
#>>>


::tcltest::cleanupTests
return

# vim: ft=tcl foldmethod=marker foldmarker=<<<,>>> ts=4 shiftwidth=4
